import { foldTo } from 'fractal-objects';

/**
 * Interface that must be implemented by all the feature modules
 * in the application.
 *
 */
export interface ModuleShape {
  /**
   * A hook called on the very start of the application,
   * right after all the feature modules are imported.
   */
  onAppCreate?: Array<(modules: Module, entryModule: NodeModule) => Promise<any>>;
}

/**
 * This class implements `ModuleShape` interface.
 *
 * It is a very minimalistic feature module implementation, that
 * has only `createApp` function.
 */
class Module implements ModuleShape {
  /**
   * A hook called on the very start of the application,
   * right after all the feature modules are imported.
   */
  public onAppCreate?: Array<(modules: Module, entryModule: NodeModule) => Promise<any>>;

  /**
   * Constructs feature module representation, that folds all the feature modules
   * into a single module represented by this instance.
   *
   * @param modules feature modules
   */
  constructor(...modules: ModuleShape[]) {
    foldTo(this, modules);
  }

  /**
   * Calls each feature module `onAppCreate` hook, with `module` object as a first argument
   * (see below) and the list of all the feature modules as a second argument,
   * extending this `ModuleShape` interface.
   *
   * In `onAppCreate` hook each module can initialize itself. Usually the `core` feature
   * module in `onAppCreate` hook initializes used framework and registers callbacks
   * exported by other feature modules. And then the framework calls these callbacks.
   *
   * @param entryModule a `module` object generated by Webpack for the very first entry javascript file
   *    of the application. Webpack exposes Webpack Hot Module Replacement API in `module.hot` for
   *    each javascript file. To have maximum control we need only entry file `module.hot` object.
   *    We use this API to quickly and properly reload application code after recompilation happens
   *    during development. `module.hot` is not available, when application code is built for
   *    production mode.
   */
  public async createApp(entryModule: NodeModule) {
    if (this.onAppCreate) {
      for (const callback of this.onAppCreate) {
        await callback(this, entryModule);
      }
    }
  }
}

export default Module;
